{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<P1>Python量化策略风险指标</header><br>\n",
    "https://mp.weixin.qq.com/s/n9YAcjdpFpAAI4SvR1aGtg"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#先引入后面可能用到的包（package）\n",
    "import pandas as pd  \n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline   \n",
    "\n",
    "# 抽象出列名映射为独立的函数\n",
    "def column_name_mapping(df: pd.DataFrame, mapping: dict) -> pd.DataFrame:\n",
    "    df.rename(columns=mapping, inplace=True)\n",
    "    return df\n",
    "\n",
    "#正常显示画图时出现的中文和负号\n",
    "from pylab import mpl\n",
    "mpl.rcParams['font.sans-serif']=['SimHei']\n",
    "mpl.rcParams['axes.unicode_minus']=False\n",
    "\n",
    "### 获取数据：tushare开源库（确认已安装好：pip install tushare）\n",
    "import akshare as ak\n",
    "#起始和结束日期可以自行输入，否则使用默认\n",
    "def get_data(code,start_date=\"2008-01-01\", end_date=\"2024-06-14\"):\n",
    "    import baostock as bs\n",
    "\n",
    "    #### 登陆系统 ####\n",
    "    bs.login()\n",
    "    rs = bs.query_history_k_data_plus(code,\n",
    "    \"date,code,open,high,low,close,preclose,volume,amount,adjustflag,turn,tradestatus,pctChg\",\n",
    "    start_date=start_date, end_date=end_date,\n",
    "    frequency=\"d\", adjustflag=\"3\")\n",
    "    \n",
    "    data_list = []\n",
    "    while (rs.error_code == '0') & rs.next():\n",
    "        # 获取一条记录，将记录合并在一起\n",
    "        data_list.append(rs.get_row_data())\n",
    "    df = pd.DataFrame(data_list, columns=rs.fields)\n",
    "    df[\"date\"] = pd.to_datetime(df[\"date\"])\n",
    "    df.set_index(\"date\", inplace=True)\n",
    "    df[\"close\"] = pd.to_numeric(df[\"close\"])\n",
    "    return df.close\n",
    "#返回收盘价\n",
    "\n",
    "#以上证综指、贵州茅台、工商银行、中国平安为例\n",
    "stocks={'sh.000001':'上证综指','sh.600519':'贵州茅台',\n",
    "        'sh.601398':'工商银行','sh.601318':'中国平安'}\n",
    "#获取上述股票（指数）的每日前复权收盘价\n",
    "df=pd.DataFrame()\n",
    "for code,name in stocks.items():\n",
    "    df[name]=get_data(code)\n",
    "\n",
    "df.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#以第一交易日2009年1月5日收盘价为基点，计算净值\n",
    "df_new=df/df.iloc[0]\n",
    "#将上述股票在回测期间内的净值可视化\n",
    "df_new.plot(figsize=(16,7))\n",
    "#图标题\n",
    "plt.title('股价净值走势',fontsize=15)\n",
    "#设置x轴坐标\n",
    "my_ticks = pd.date_range('2008-01-01','2024-06-14',freq='Y')\n",
    "plt.xticks(my_ticks,fontsize=12)\n",
    "#去掉上、右图的线\n",
    "ax=plt.gca()\n",
    "ax.spines['right'].set_color('none')\n",
    "ax.spines['top'].set_color('none')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "### 区间累计收益率(绝对收益率)\n",
    "total_ret=df_new.iloc[-1]-1\n",
    "TR=pd.DataFrame(total_ret.values,columns=['累计收益率'],index=total_ret.index)\n",
    "TR"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#numpy:np.maximum.accumulate计算序列累计最大值\n",
    "code='上证综指'\n",
    "n_d=((np.maximum.accumulate(df[code])-df[code])/np.maximum.accumulate(df[code])).max()\n",
    "#pandas使用cummax（）计算序列累计最大值\n",
    "p_d=((df[code].cummax()-df[code])/df[code].cummax()).max()\n",
    "#打印结果\n",
    "print(f'numpy方法计算结果：{round(n_d*100,2)}%')\n",
    "print(f'pandas方法计算结果：{round(p_d*100,2)}%') "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "###年化收益率,假设一年以250交易日计算\n",
    "annual_ret=pow(1+total_ret,250/len(df_new))-1\n",
    "AR=pd.DataFrame(annual_ret.values,columns=['年化收益率'],index=annual_ret.index)\n",
    "AR"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#定义成函数，减少重复工作\n",
    "def max_drawdown(df):\n",
    "    md=((df.cummax()-df)/df.cummax()).max()\n",
    "    return round(md,4)\n",
    "md={}\n",
    "for code,name in stocks.items():\n",
    "    md[name]=max_drawdown(df[name])\n",
    "#最大回撤率结果：\n",
    "MD=pd.DataFrame(md,index=['最大回撤']).T"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#计算每日收益率\n",
    "#收盘价缺失值（停牌），使用前值代替\n",
    "rets=(df.fillna(method='pad')).apply(lambda x:x/x.shift(1)-1)[1:]\n",
    "rets.head()\n",
    "\n",
    "#市场指数为x，个股收益率为y\n",
    "from scipy import stats\n",
    "x=rets.iloc[:,0].values\n",
    "y=rets.iloc[:,1:].values\n",
    "AB=pd.DataFrame()\n",
    "alpha=[]\n",
    "beta=[]\n",
    "for i in range(3):\n",
    "#使用scipy库中的stats.linregress线性回归\n",
    "#python回归有多种实现方式，\n",
    "#如statsmodels.api的OLS，sklearn库等等\n",
    "    b,a,r_value,p_value,std_err=stats.linregress(x,y[:,i])\n",
    "    #alpha转化为年化\n",
    "    alpha.append(round(a*250,3))\n",
    "    beta.append(round(b,3))\n",
    "AB['alpha']=alpha\n",
    "AB['beta']=beta\n",
    "AB.index=rets.columns[1:]\n",
    "#输出结果："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#使用公式法直接计算beta值（见前文公式）：\n",
    "beta1=rets[['上证综指','贵州茅台']].cov().iat[0,1]/rets['上证综指'].var()\n",
    "beta2=rets[['上证综指','工商银行']].cov().iat[0,1]/rets['上证综指'].var()\n",
    "beta3=rets[['上证综指','中国平安']].cov().iat[0,1]/rets['上证综指'].var()\n",
    "print(f'贵州茅台beta:{round(beta1,3)}')\n",
    "print(f'工商银行beta:{round(beta2,3)}')\n",
    "print(f'中国平安beta:{round(beta3,3)}')\n",
    "\n",
    "\n",
    "#使用公式法直接计算beta值（见前文公式）：\n",
    "#annual_ret是前文计算出来的年化收益率\n",
    "alpha1=(annual_ret[1]-annual_ret[0]*beta1)\n",
    "alpha2=(annual_ret[2]-annual_ret[0]*beta2)\n",
    "alpha3=(annual_ret[3]-annual_ret[0]*beta3)\n",
    "print(f'贵州茅台alpha:{round(alpha1,3)}')\n",
    "print(f'工商银行alpha:{round(alpha2,3)}')\n",
    "print(f'中国平安alpha:{round(alpha3,3)}')\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#超额收益率以无风险收益率为基准\n",
    "#假设无风险收益率为年化3%\n",
    "exReturn=rets-0.03/250\n",
    "#计算夏普比率\n",
    "sharperatio=np.sqrt(len(exReturn))*exReturn.mean()/exReturn.std()\n",
    "#夏普比率的输出结果\n",
    "SHR=pd.DataFrame(sharperatio,columns=['夏普比率'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "###信息比率\n",
    "#超额收益率以指数收益率或其他为基准\n",
    "#这里以上证综指为基准\n",
    "ex_return=pd.DataFrame() \n",
    "ex_return['贵州茅台']=rets.iloc[:,1]-rets.iloc[:,0]\n",
    "ex_return['工商银行']=rets.iloc[:,2]-rets.iloc[:,0]\n",
    "ex_return['中国平安']=rets.iloc[:,3]-rets.iloc[:,0]\n",
    "\n",
    "#计算信息比率\n",
    "information=np.sqrt(len(ex_return))*ex_return.mean()/ex_return.std()\n",
    "#信息比率的输出结果\n",
    "INR=pd.DataFrame(information,columns=['信息比率'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "indicators=pd.concat([TR,AR,MD,AB,SHR,INR],axis=1,\n",
    "    join='outer',sort=False)\n",
    "#结果保留三位小数\n",
    "indicators.round(3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "def plot_max_drawdown(data, symbol, trading_days_per_year=252, show_legend=True):\n",
    "    \"\"\"\n",
    "    绘制资产的最大回撤、累计收益曲线、年化收益率、波动率和夏普比率。\n",
    "    \n",
    "    参数:\n",
    "    - data: 包含资产价格的Pandas DataFrame。\n",
    "    - symbol: 要分析的资产代码。\n",
    "    - trading_days_per_year: 每年的交易日数，默认为252。\n",
    "    - show_legend: 是否在图表中显示图例，默认为True。\n",
    "    \n",
    "    功能:\n",
    "    - 计算日收益率。\n",
    "    - 绘制累计收益曲线。\n",
    "    - 标识最大回撤区间。\n",
    "    - 计算并显示年化收益率、波动率和夏普比率。\n",
    "    \"\"\"\n",
    "    # 验证symbol是否存在\n",
    "    if symbol not in data.columns:\n",
    "        raise ValueError(f\"Symbol '{symbol}' not found in the provided data.\")\n",
    "    \n",
    "    # 计算日收益率\n",
    "    returns = data[symbol].pct_change().dropna()\n",
    "    \n",
    "    # 累计收益曲线\n",
    "    equity_curve = (1 + returns).cumprod()\n",
    "    \n",
    "    # 最大回撤计算\n",
    "    cumulative_returns = equity_curve\n",
    "    max_return_idx = cumulative_returns.cummax().idxmax()\n",
    "    max_drawdown_series = (cumulative_returns - cumulative_returns.loc[max_return_idx]) / cumulative_returns.loc[max_return_idx]\n",
    "    max_dd = max_drawdown_series.max() * 100\n",
    "    start_dd, end_dd = max_drawdown_series.idxmax(), cumulative_returns.idxmin()\n",
    "    \n",
    "    # 性能指标计算\n",
    "    annual_return = (equity_curve.iloc[-1]) ** (trading_days_per_year / len(returns)) - 1\n",
    "    volatility_annual = returns.std() * np.sqrt(trading_days_per_year)\n",
    "    sharpe_ratio = annual_return / volatility_annual if volatility_annual != 0 else np.nan\n",
    "    \n",
    "    # 绘图\n",
    "    plt.figure(figsize=(14, 7))\n",
    "    plt.plot(equity_curve, label='Equity Curve', linewidth=2)\n",
    "    plt.fill_between(equity_curve.index, equity_curve, where=equity_curve.index.to_series().between(start_dd, end_dd), \n",
    "                     color='red', alpha=0.3, label=f'Max Drawdown ({max_dd:.2f}%)')\n",
    "    plt.title(f'Performance Metrics for {symbol}: Annual Return {annual_return*100:.2f}%, Volatility {volatility_annual*100:.2f}%, Sharpe Ratio {sharpe_ratio:.2f}')\n",
    "    plt.xlabel('Date')\n",
    "    plt.ylabel('Cumulative Returns')\n",
    "    \n",
    "    if show_legend:\n",
    "        plt.legend(loc='best')\n",
    "    plt.grid(True)\n",
    "    plt.show()\n",
    "\n",
    "# 示例调用\n",
    "# 假设`df`是一个包含金融数据的DataFrame，且`symbols`是要分析的资产代码\n",
    "# plot_max_drawdown(df, symbol='AAPL')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#贵州茅台买入持有策略回测可视化\n",
    "plot_max_drawdown(df,'贵州茅台')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#工商银行买入持有策略回测可视化\n",
    "plot_max_drawdown(df,'工商银行')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#中国平安买入持有策略回测可视化\n",
    "plot_max_drawdown(df,'中国平安')"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "tf",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
